﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
using Brain.Skinning;

namespace Brain
{
    public class WorldObject : IModeled, IWorldObject, IUpdateable, IDrawable, IBaseComponent
    {
        public Color? BoundingBoxColor { get; set; }

        public virtual object Model { get; set; }

        public object PickingTag { get; set; }


        public bool ShouldSerialize { get; set; }
        protected string modelPlace;
        public virtual string ModelPlace
        {
            get { return modelPlace; }
            set
            {
                modelPlace = value;
                if (Path.GetExtension(value).ToLower().EndsWith(".bmodel"))
                {
                    Model = Engine.Instance.ContentManager.Load<BrainModel>(value);
                }
                else
                {
                    if (!string.IsNullOrEmpty(value))
                    {
                        WorldObjectRendering status = this.Rendering;
                        this.Rendering = WorldObjectRendering.Invisible;

                        string name = value.ToLower();
                        if (name.StartsWith("skin:"))
                        {
                            string m = name.Remove(0, 5);

                            Model = Engine.Instance.ContentManager.Load<SkinnedModel>(m);
                        }
                        else
                        {
                            Model = Engine.Instance.ContentManager.Load<Model>(value);
                        }

                        this.Rendering = status;
                    }
                }
            }
        }

        public BoundingBox BoundingBox { get; set; }
        public BoundingSphere BoundingSphere { get; set; }

        public string Name { get; set; }

        public bool ShouldUpdate { get; set; }
        public bool ShouldDraw { get; set; }

        /// <summary>
        /// If it should calculate the World Matrix
        /// </summary>
        public bool CalculateWorldMatrix { get; protected set; }

        /// <summary>
        /// If it should calculate the Rotation Matrix
        /// </summary>
        public bool CalculateRotationMatrix { get; protected set; }

        /// <summary>
        /// If it should Calculate the Scale Matrix
        /// </summary>
        public bool CalculateScaleMatrix { get; protected set; }

        /// <summary>
        /// If it should calculate the Translation Matrix
        /// </summary>
        public bool CalculateTranslationMatrix { get; protected set; }

        /// <summary>
        /// The Rotation of this Object, in Degrees. This
        /// just converts to the RadiansRotation property.
        /// </summary>
        public Vector3 DegreesRotation
        {
            get { return Vector3Util.ToDegrees(RotationRadians); }
            set { RotationRadians = Vector3Util.ToRadians(value); }
        }

        /// <summary>
        /// The Last Position of this World Object
        /// </summary>
        public Vector3 LastPosition;

        private Vector3 pos;
        /// <summary>
        /// The Position of this Object
        /// </summary>
        public Vector3 Position
        {
            get { return pos; }
            set
            {
                LastPosition = pos;
                pos = value;

                if (PhysicsObject != null)
                {
                    PhysicsObject.Position = value;
                }
            }
        }
        protected Vector3 lastPosition { get; set; }

        /// <summary>
        /// Represents the Rotation of this Object
        /// </summary>
        public Matrix RotationMatrix
        {
            get { return rotationMatrix; }
            set
            {
                rotationMatrix = value;
                CalculateRotationMatrix = false;
            }
        }
        protected Matrix rotationMatrix;

        /// <summary>
        /// The Rotation of this Object, in Radians
        /// </summary>
        public Vector3 RotationRadians
        {
            get { return rotationRadians; }
            set 
            {
                LastRotation = rotationRadians;
                rotationRadians = value; 
            }
        }
        protected Vector3 rotationRadians;
        public Vector3 LastRotation { get; protected set; }

        /// <summary>
        /// Represents the Scale of this Object
        /// </summary>
        public Matrix ScaleMatrix
        {
            get { return scaleMatrix; }
            set
            {
                scaleMatrix = value;
                CalculateScaleMatrix = false;
            }
        }
        protected Matrix scaleMatrix;

        /// <summary>
        /// The Scale of this World Object
        /// </summary>
        public Vector3 Scale
        {
            get { return scale; }
            set
            {
                lastScale = scale;
                scale = value;

                Vector3 dif = scale - lastScale;
                BoundingBox = new BoundingBox(
                    BoundingBox.Min * dif,
                    BoundingBox.Max * dif);

                if (PhysicsObject != null)
                {
                    PhysicsObject.Scale = value;
                }
            }
        }
        protected Vector3 scale = Vector3.One;
        protected Vector3 lastScale { get; set; }

        /// <summary>
        /// Tag 1 of this Object
        /// </summary>
        public object Tag1 { get; set; }
        /// <summary>
        /// Tag 2 of this Object
        /// </summary>
        public object Tag2 { get; set; }
        /// <summary>
        /// Tag 3 of this Object
        /// </summary>
        public object Tag3 { get; set; }
        /// <summary>
        /// Reserved Tag for Picking classes
        /// </summary>
        public IPhysicsObject PhysicsObject { get; set; }
        /// <summary>
        /// Reserved Tag for Rendering Classes
        /// </summary>
        public object RenderingTag { get; set; }


        private WorldObjectRendering rendering;
        public WorldObjectRendering Rendering
        {
            get { return rendering; }
            set
            {
                if (Engine.Instance.RenderManager3D != null)
                {
                    if (value == WorldObjectRendering.Invisible &&
                        rendering != value)
                    {
                        if (!Engine.Instance.RunningEditor)
                        {
                            Engine.Instance.RenderManager3D.RemoveWorldObject(this);
                        }
                    }
                    rendering = value;

                    if (rendering == WorldObjectRendering.Rendered ||
                        rendering == WorldObjectRendering.EditorVisible && Engine.Instance.Editor != null)
                    {
                        Engine.Instance.RenderManager3D.SubmitWorldObject(this);
                    }
                }
            }
        }

        /// <summary>
        /// Represents the Translation of this Object
        /// </summary>
        public Matrix TranslationMatrix
        {
            get { return translationMatrix; }
            set
            {
                translationMatrix = value;
                CalculateTranslationMatrix = false;
            }
        }
        protected Matrix translationMatrix;

        /// <summary>
        /// WorldMatrix of this Object. It's the
        /// multiplication of the
        /// Scale * Rotation * Translation Matrixes
        /// </summary>
        public Matrix WorldMatrix
        {
            get { return worldMatrix; }
            set
            {
                worldMatrix = value;
                CalculateWorldMatrix = false;
            }
        }
        protected Matrix worldMatrix;


        private WorldObjectPhysics physics;
        public WorldObjectPhysics PhysicsType
        {
            get
            {
                return physics;
            }
            set
            {
                WorldObjectPhysics lastState = physics;
                physics = value;
                if (Engine.Instance.Physics3DManager != null)
                {
                    Engine.Instance.Physics3DManager.ChangedState(this, lastState);
                }
            }
        }

        private SkinnedAnimationPlayer player;
        public SkinnedAnimationPlayer DefaultPlayer
        {
            get { return player; }
            set { player = value; }
        }
        private bool usingDefaultPlayer;
        public virtual void StartDefaultPlayer(SkinnedAnimationClip clip)
        {
            if (!(this.Model is SkinnedModel))
            {
                throw new Exception("Model is not a Skinned Model!");
            }
            player = new SkinnedAnimationPlayer(this.Model as SkinnedModel, clip);
            usingDefaultPlayer = true;
        }


        public WorldObject()
        {
            SetDefaultValues();

            ShouldUpdate = true;
            ShouldDraw = true;
            ShouldSerialize = true;
        }
        public WorldObject(string modelPlace)
        {
            this.ModelPlace = modelPlace;
            SetDefaultValues();

            ShouldUpdate = true;
            ShouldDraw = true;
            ShouldSerialize = true;
        }
        public WorldObject(GameScreen gs)
        {
            gs.AddComponent(this);
            SetDefaultValues();

            ShouldUpdate = true;
            ShouldDraw = true;
            ShouldSerialize = true;
        }
        public WorldObject(string modelPlace, GameScreen gs)
        {
            this.ModelPlace = modelPlace;

            gs.AddComponent(this);
            SetDefaultValues();

            ShouldUpdate = true;
            ShouldDraw = true;
            ShouldSerialize = true;
        }
        private void SetDefaultValues()
        {
            this.rotationMatrix = Matrix.Identity;
            this.translationMatrix = Matrix.Identity;
            this.scaleMatrix = MatrixUtil.UnitScaleMatrix;
            this.worldMatrix = this.scaleMatrix;

            this.BoundingBox = new BoundingBox(Vector3.Zero, Vector3.One);
            this.BoundingSphere = new BoundingSphere(Vector3.Zero, 1);

            this.SetCalculate(true, true, true, true);
        }

        public virtual void Dispose()
        {
        }

        public virtual void Update()
        {
            if (this.physics != WorldObjectPhysics.NoPhysics &&
                this.PhysicsObject != null)
            {
                this.WorldMatrix =
                    MatrixUtil.CreateWorldMatrix(Matrix.CreateScale(this.Scale),
                    MatrixUtil.CreateRotationMatrix(this.RotationRadians),
                    Matrix.CreateTranslation(PhysicsObject.Position));
            }

            if (usingDefaultPlayer)
            {
                player.Update();
            }
        }

        public virtual void Draw()
        {
            bool changed = false;
            if (CalculateTranslationMatrix)
            {
                if (lastPosition != Position)
                {
                    translationMatrix = Matrix.CreateTranslation(Position);
                    changed = true;
                    lastPosition = Position;
                }
            }
            if (CalculateScaleMatrix)
            {
                if (Scale != lastScale)
                {
                    scaleMatrix = Matrix.CreateScale(Scale);
                    changed = true;
                    lastScale = Scale;
                }
            }
            if (CalculateRotationMatrix)
            {
                if (RotationRadians != LastRotation)
                {
                    rotationMatrix = MatrixUtil.CreateRotationMatrix(RotationRadians);
                    changed = true;
                    LastRotation = RotationRadians;
                }
            }

            if (CalculateWorldMatrix)
            {
                if (changed)
                {
                    worldMatrix = scaleMatrix * RotationMatrix * translationMatrix;
                }
            }

            if (Engine.Instance.RenderManager3D != null)
            {
                IRenderManager3D render = Engine.Instance.RenderManager3D;
                render.UpdateWorldObjectStatus(this);
            }
        }

        public virtual void SetCalculate(bool calculateSize,
            bool calculateRot, bool calculateTrans, bool calculateWorld)
        {
            this.CalculateScaleMatrix = calculateSize;
            this.CalculateRotationMatrix = calculateRot;
            this.CalculateTranslationMatrix = calculateTrans;
            this.CalculateWorldMatrix = calculateWorld;
        }


        public void SerializeData(SerializationData parentData, int index)
        {
            parentData.AddData("WO.BBox", this.BoundingBox, index);
            if (this.BoundingBoxColor != null)
            {
                parentData.AddData("WO.BBoxColor", this.BoundingBoxColor, index);
            }
            parentData.AddData("WO.BSphere", this.BoundingSphere, index);
            parentData.AddData("WO.CalcRotMatrix", this.CalculateRotationMatrix, index);
            parentData.AddData("WO.CalcScaMatrix", this.CalculateScaleMatrix, index);
            parentData.AddData("WO.CalcTransMatrix", this.CalculateTranslationMatrix, index);
            parentData.AddData("WO.CalcWorldMatrix", this.CalculateWorldMatrix, index);
            parentData.AddData("WO.ModelPlace", this.ModelPlace, index);
            parentData.AddData("WO.Name", this.Name, index);
            parentData.AddData("WO.Physics", this.PhysicsType, index);
            parentData.AddData("WO.Position", this.Position, index);
            parentData.AddData("WO.Rendering", this.Rendering, index);
            if (!this.CalculateRotationMatrix)
            {
                parentData.AddData("WO.RotMatrix", this.RotationMatrix, index);
            }
            if (!this.CalculateScaleMatrix)
            {
                parentData.AddData("WO.ScaMatrix", this.ScaleMatrix, index);
            }
            if (!this.CalculateTranslationMatrix)
            {
                parentData.AddData("WO.TransMatrix", this.TranslationMatrix, index);
            }
            if (!this.CalculateWorldMatrix)
            {
                parentData.AddData("WO.WorldMatrix", this.WorldMatrix, index);
            }
            parentData.AddData("WO.Rot", this.RotationRadians, index);
            parentData.AddData("WO.Sca", this.Scale, index);
            parentData.AddData("WO.Draw", this.ShouldDraw, index);
            parentData.AddData("WO.Update", this.ShouldUpdate, index);
        }

        public void DeserializeData(SerializationData parentData, int index)
        {
            this.BoundingBox = parentData.GetData<BoundingBox>("WO.BBox", index);
            this.BoundingBoxColor = parentData.GetData<Color?>("WO.BBoxColor", index);
            this.BoundingSphere = parentData.GetData<BoundingSphere>("WO.BSphere", index);
            this.CalculateRotationMatrix = parentData.GetData<bool>("WO.CalcRotMatrix", index);
            this.CalculateScaleMatrix = parentData.GetData<bool>("WO.CalcScaMatrix", index);
            this.CalculateTranslationMatrix = parentData.GetData<bool>("WO.CalcTransMatrix", index);
            this.CalculateWorldMatrix = parentData.GetData<bool>("WO.CalcWorldMatrix", index);
            this.Name = parentData.GetData<string>("WO.Name", index);
            this.PhysicsType = parentData.GetData<WorldObjectPhysics>("WO.Physics", index);
            this.Position = parentData.GetData<Vector3>("WO.Position", index);
            this.RotationMatrix = parentData.GetData<Matrix>("WO.RotMatrix", index);
            this.scaleMatrix = parentData.GetData<Matrix>("WO.ScaMatrix", index);
            this.translationMatrix = parentData.GetData<Matrix>("WO.TransMatrix", index);
            this.worldMatrix = parentData.GetData<Matrix>("WO.WorldMatrix", index);
            this.RotationRadians = parentData.GetData<Vector3>("WO.Rot", index);
            this.Scale = parentData.GetData<Vector3>("WO.Sca", index);
            this.ShouldDraw = parentData.GetData<bool>("WO.Draw", index);
            this.ShouldUpdate = parentData.GetData<bool>("WO.Update", index);

            this.ModelPlace = parentData.GetData<string>("WO.ModelPlace", index);
            this.Rendering = parentData.GetData<WorldObjectRendering>("WO.Rendering", index);

            translationMatrix = Matrix.Identity;
            scaleMatrix = Matrix.Identity;
            rotationMatrix = Matrix.Identity;
            worldMatrix = Matrix.Identity;
            if (CalculateTranslationMatrix)
            {
                translationMatrix = Matrix.CreateTranslation(Position);
            }
            if (CalculateScaleMatrix)
            {
                scaleMatrix = Matrix.CreateScale(Scale);
            }
            if (CalculateRotationMatrix)
            {
                rotationMatrix = MatrixUtil.CreateRotationMatrix(RotationRadians);
            }
            if (CalculateWorldMatrix)
            {
                worldMatrix = scaleMatrix * RotationMatrix * translationMatrix;
            }
        }

    }
}
